/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.replica.replicaisland;

/**
 * Manages a double-buffered queue of renderable objects. The game thread
 * submits drawable objects to the the active render queue while the render
 * thread consumes drawables from the alternate queue. When both threads
 * complete a frame the queues are swapped. Note that this class can manage any
 * number (>=2) of render queues, but increasing the number over two means that
 * the game logic will be running significantly ahead of the rendering thread,
 * which may make the user feel that the controls are "loose."
 */
public class RenderSystem extends BaseObject {
	private static final int TEXTURE_SORT_BUCKET_SIZE = 1000;
	private RenderElementPool mElementPool;
	private ObjectManager[] mRenderQueues;
	private int mQueueIndex;

	private final static int DRAW_QUEUE_COUNT = 2;
	private final static int MAX_RENDER_OBJECTS_PER_FRAME = 384;
	private final static int MAX_RENDER_OBJECTS = MAX_RENDER_OBJECTS_PER_FRAME
			* DRAW_QUEUE_COUNT;

	public RenderSystem() {
		super();
		mElementPool = new RenderElementPool(MAX_RENDER_OBJECTS);
		mRenderQueues = new ObjectManager[DRAW_QUEUE_COUNT];
		for (int x = 0; x < DRAW_QUEUE_COUNT; x++) {
			mRenderQueues[x] = new PhasedObjectManager(
					MAX_RENDER_OBJECTS_PER_FRAME);
		}
		mQueueIndex = 0;
	}

	@Override
	public void reset() {

	}

	public void scheduleForDraw(DrawableObject object, Vector2 position,
			int priority, boolean cameraRelative) {
		RenderElement element = mElementPool.allocate();
		if (element != null) {
			element.set(object, position, priority, cameraRelative);
			mRenderQueues[mQueueIndex].add(element);
		}
	}

	private void clearQueue(FixedSizeArray<BaseObject> objects) {
		final int count = objects.getCount();
		final Object[] objectArray = objects.getArray();
		final RenderElementPool elementPool = mElementPool;
		for (int i = count - 1; i >= 0; i--) {
			RenderElement element = (RenderElement) objectArray[i];
			elementPool.release(element);
			objects.removeLast();
		}

	}

	public void swap(GameRenderer renderer, float cameraX, float cameraY) {
		mRenderQueues[mQueueIndex].commitUpdates();

		// This code will block if the previous queue is still being executed.
		renderer.setDrawQueue(mRenderQueues[mQueueIndex], cameraX, cameraY);

		final int lastQueue = (mQueueIndex == 0) ? DRAW_QUEUE_COUNT - 1
				: mQueueIndex - 1;

		// Clear the old queue.
		FixedSizeArray<BaseObject> objects = mRenderQueues[lastQueue]
				.getObjects();
		clearQueue(objects);

		mQueueIndex = (mQueueIndex + 1) % DRAW_QUEUE_COUNT;
	}

	/*
	 * Empties all draw queues and disconnects the game thread from the
	 * renderer.
	 */
	public void emptyQueues(GameRenderer renderer) {
		renderer.setDrawQueue(null, 0.0f, 0.0f);
		for (int x = 0; x < DRAW_QUEUE_COUNT; x++) {
			mRenderQueues[x].commitUpdates();
			FixedSizeArray<BaseObject> objects = mRenderQueues[x].getObjects();
			clearQueue(objects);

		}
	}

	public class RenderElement extends PhasedObject {
		public RenderElement() {
			super();
		}

		public void set(DrawableObject drawable, Vector2 position,
				int priority, boolean isCameraRelative) {
			mDrawable = drawable;
			x = position.x;
			y = position.y;
			cameraRelative = isCameraRelative;
			final int sortBucket = priority * TEXTURE_SORT_BUCKET_SIZE;
			int sortOffset = 0;
			if (drawable != null) {
				Texture tex = drawable.getTexture();
				if (tex != null) {
					sortOffset = (tex.resource % TEXTURE_SORT_BUCKET_SIZE)
							* Utils.sign(priority);
				}
			}
			setPhase(sortBucket + sortOffset);
		}

		public void reset() {
			mDrawable = null;
			x = 0.0f;
			y = 0.0f;
			cameraRelative = false;
		}

		public DrawableObject mDrawable;
		public float x;
		public float y;
		public boolean cameraRelative;
	}

	protected class RenderElementPool extends TObjectPool<RenderElement> {

		RenderElementPool(int max) {
			super(max);
		}

		@Override
		public void release(Object element) {
			RenderElement renderable = (RenderElement) element;
			// if this drawable came out of a pool, make sure it is returned to
			// that pool.
			final ObjectPool pool = renderable.mDrawable.getParentPool();
			if (pool != null) {
				pool.release(renderable.mDrawable);
			}
			// reset on release
			renderable.reset();
			super.release(element);
		}

		@Override
		protected void fill() {
			for (int x = 0; x < getSize(); x++) {
				getAvailable().add(new RenderElement());
			}
		}
	}
}
